﻿///<reference path="_references.ts" />
///<reference path="DrawLoop.ts" />

module ScrambledEggs {

    export class Glyph {
        constructor(public LevelCharacter: string, public IsWalkable: () => bool, public DisplayCharacter: string = null, public DisplayColor: string = null, public EntryFunction: () => void = null) {
            if (this.DisplayCharacter == null)
                this.DisplayCharacter = this.LevelCharacter;
            if (this.DisplayColor == null)
                this.DisplayColor = "#FFFFFF";
            if (this.EntryFunction == null)
                this.EntryFunction = $.noop;
        }
    }

    export class Coordinate {
        constructor(public X: number, public Y: number) {
        }
        public Clone() {
            return new Coordinate(this.X, this.Y);
        }
    }

    export class Tile {
        constructor(public Position: Coordinate, public Glyph: Glyph) {
        }
    }

    export class Adventure implements IDrawable {
        public Playfield: Tile[][];
        public GlyphField: { [glyphChar: string]: Tile[]; };
        public CharacterGlyph: Glyph;
        public Ctx: CanvasRenderingContext2D;

        public GlyphWidth: number = 15;
        public GlyphHeight: number = 15;

        private _canvasGlyphHeight;
        private _canvasGlyphWidth;
        private _fieldGlyphHeight;
        private _fieldGlyphWidth;

        public Position: Coordinate;
        public OldPosition: Coordinate;
        public MoveSpeed = 300;
        public MoveStartTime = 0;
        public MoveEndTime = 0;
        public TextColor = "rgb(20, 0, 0)";

        constructor(level: string, glyphs: { [char: string]: Glyph; }, characterGlyph: Glyph, public Canvas: Element) {
            var me = this;
            me.Ctx = (<any>Canvas).getContext("2d");
            me.Ctx.font = "14px Courier New";

            me.CharacterGlyph = characterGlyph;
            me._canvasGlyphHeight = Math.floor((<any>Canvas).height / me.GlyphHeight);
            me._canvasGlyphWidth = Math.floor((<any>Canvas).width / me.GlyphWidth);

            glyphs["+"] = new Glyph("+", () => { return false; });
            glyphs["/"] = new Glyph(" ", () => { return false; });
            if (glyphs[" "] == null)
                glyphs[" "] = new Glyph(" ", () => { return true; });

            me.GlyphField = {};
            for (var char in glyphs) {
                var glyph = glyphs[char];
                if (glyph.LevelCharacter != char && char != "/")
                    throw "Glyph level character is inconsistent: " + char + "->" + glyph.LevelCharacter;
                me.GlyphField[char] = [];
            }

            me.Playfield = [];
            var rows = level.split("\n");
            var rowCounter = 0;
            var rowWidth = 0;
            
            for (var y = 0, ylen = rows.length; y < ylen; y++) {
                var row = rows[y].trim();

                if (me._fieldGlyphWidth == null)
                    me._fieldGlyphWidth = row.length;
                if (row.length != me._fieldGlyphWidth)
                    throw "row " + (row + 1) + "has " + row.length + " characters instead of " + me._fieldGlyphWidth;

                me.Playfield.push([]);
                var playfieldRow = me.Playfield[rowCounter];
                for (var x = 0, xlen = row.length; x < xlen; x++) {
                    var glyph = glyphs[row[x]];

                    if (glyph == null) {
                        if (characterGlyph.LevelCharacter == row[x]) {
                            glyph = glyphs[" "];
                            me.Position = new Coordinate(x, rowCounter);
                        }
                        else {
                            console.log("Unrecognized glyph in field: '" + row[x] + "' at row " + y + " col " + (x + 1));
                            glyph = new Glyph("/", () => { return false; }, row[x].toUpperCase(), me.TextColor, $.noop);
                        }
                    }
                    var tile = new Tile(new Coordinate(x, rowCounter), glyph);
                    playfieldRow.push(tile);
                    me.GlyphField[glyph.LevelCharacter].push(tile);
                }
                rowCounter++;
            }
            me._fieldGlyphHeight = me.Playfield.length;
        }

        Start() {
            var me = this;
            var loop: DrawLoop = new DrawLoop(function (state: ILoopState) { me.Update(state); });
            loop.setDrawables([this]);
        }

        Update(state: ILoopState) {
            var me = this;
            var time = state.Time();
            if (time >= me.MoveEndTime) {
                var keyState = state.GetKeyState();
                var moved = false;
                if (keyState.KeyArrowUp()) {
                    if (me.Position.Y > 0 && me.Playfield[me.Position.Y - 1][me.Position.X].Glyph.IsWalkable()) {
                        me.OldPosition = me.Position.Clone();
                        me.MoveStartTime = time;
                        me.MoveEndTime = time + me.MoveSpeed;
                        me.Position.Y--;
                        moved = true;
                    }
                }
                else if (keyState.KeyArrowDown()) {
                    if (me.Position.Y < me._fieldGlyphHeight && me.Playfield[me.Position.Y + 1][me.Position.X].Glyph.IsWalkable()) {
                        me.OldPosition = me.Position.Clone();
                        me.MoveStartTime = time;
                        me.MoveEndTime = time + me.MoveSpeed;
                        me.Position.Y++;
                        moved = true;
                    }
                }
                if (keyState.KeyArrowRight()) {
                    if (me.Position.X < me._fieldGlyphWidth - 1 && me.Playfield[me.Position.Y][me.Position.X + 1].Glyph.IsWalkable()) {
                        if (moved) {
                            me.MoveEndTime = time + me.MoveSpeed * 1.7;
                            me.Position.X++;
                        }
                        else {
                            me.OldPosition = me.Position.Clone();
                            me.MoveStartTime = time;
                            me.MoveEndTime = time + me.MoveSpeed;
                            me.Position.X++;
                            moved = true;
                        }
                    }
                }
                else if (keyState.KeyArrowLeft()) {
                    if (me.Position.X > 0 && me.Playfield[me.Position.Y][me.Position.X - 1].Glyph.IsWalkable()) {
                        if (moved) {
                            me.MoveEndTime = time + me.MoveSpeed * 1.7;
                            me.Position.X--;
                        }
                        else {
                            me.OldPosition = me.Position.Clone();
                            me.MoveStartTime = time;
                            me.MoveEndTime = time + me.MoveSpeed;
                            me.Position.X--;
                            moved = true;
                        }
                    }
                }
                if (moved) {
                    me.Playfield[me.Position.Y][me.Position.X].Glyph.EntryFunction();
                }
            }
        }

        Draw(time: number) {
            var me = this;
            var canvas = <any>me.Canvas;
            var ctx = me.Ctx;
            ctx.clearRect(0, 0, canvas.height, canvas.width);
            var color = "";

            var characterPosition = new Coordinate(Math.floor(me._canvasGlyphWidth / 2), Math.floor(me._canvasGlyphHeight / 2));
            var drawStart = new Coordinate(0, 0);
            me.SetDrawParameters(me.Position, characterPosition, drawStart);

            var interpolation = (time - me.MoveStartTime) / (me.MoveEndTime - me.MoveStartTime);
            if (interpolation > 0 && interpolation < 1) {
                var interpolationRev = 1 - interpolation;

                var characterPositionOld = new Coordinate(Math.floor(me._canvasGlyphWidth / 2), Math.floor(me._canvasGlyphHeight / 2));
                var drawStartOld = new Coordinate(0, 0);
                me.SetDrawParameters(me.OldPosition, characterPositionOld, drawStartOld);

                drawStart.X = drawStart.X * interpolation + drawStartOld.X * interpolationRev;
                drawStart.Y = drawStart.Y * interpolation + drawStartOld.Y * interpolationRev;
                characterPosition.X = characterPosition.X * interpolation + characterPositionOld.X * interpolationRev;
                characterPosition.Y = characterPosition.Y * interpolation + characterPositionOld.Y * interpolationRev;
            }
            
            for (var y = Math.floor(drawStart.Y), ylen = Math.min(Math.ceil(drawStart.Y) + 1 + me._canvasGlyphHeight, me._fieldGlyphHeight); y < ylen; y++) {
                var row = me.Playfield[y];
                for (var x = Math.floor(drawStart.X), xlen = Math.min(Math.ceil(drawStart.X) + 1 + me._canvasGlyphWidth, me._fieldGlyphWidth); x < xlen; x++) {
                    var tile = row[x];
                    if (color != tile.Glyph.DisplayColor)
                        ctx.fillStyle = color = tile.Glyph.DisplayColor;
                    ctx.fillText(tile.Glyph.DisplayCharacter, (x - drawStart.X) * me.GlyphWidth, (y - drawStart.Y) * me.GlyphHeight + me.GlyphHeight);
                }
            }
            ctx.fillStyle = me.CharacterGlyph.DisplayColor;
            ctx.fillText(me.CharacterGlyph.DisplayCharacter, characterPosition.X * me.GlyphWidth, characterPosition.Y * me.GlyphHeight + me.GlyphHeight);
        }

        private SetDrawParameters(position: Coordinate, characterPosition: Coordinate, drawStart: Coordinate) {
            var me = this;
            var tmpPerf;
            drawStart.X = position.X - characterPosition.X;
            drawStart.Y = position.Y - characterPosition.Y;

            if (drawStart.X < 0) {
                characterPosition.X += drawStart.X;
                drawStart.X = 0;
            }
            else if (drawStart.X > (tmpPerf = (me._fieldGlyphWidth - me._canvasGlyphWidth))) {
                if (tmpPerf > 0) {
                    characterPosition.X += drawStart.X - tmpPerf;
                    drawStart.X = tmpPerf;
                }
                else {
                    characterPosition.X += drawStart.X;
                    drawStart.X = 0;
                }
            }
            if (drawStart.Y < 0) {
                characterPosition.Y += drawStart.Y;
                drawStart.Y = 0;
            }
            else if (drawStart.Y > (tmpPerf = (me._fieldGlyphHeight - me._canvasGlyphHeight))) {
                if (tmpPerf > 0) {
                    characterPosition.Y += drawStart.Y - tmpPerf;
                    drawStart.Y = tmpPerf;
                }
                else {
                    characterPosition.Y += drawStart.Y;
                    drawStart.Y = 0;
                }
            }
        }
    }
}